package com.emcom.gotour.utils;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Bean auto generator.<br>
 * @author Cui Zuoli, Yu WenJie
 * @modify by Cui Zuoli at 2009-03-25 suport multi table.<br/>
 * @version 2.0
 */
public class BeanUtils {
	// ---------------------------------------------------------------
	// project name
	private static final String projectName = "gotour";
	// author
	private static final String author = "Cui Zuoli";
	// ---------------------------------------------------------------
	// database po package path
	private static final String packagePath = "package com.emcom.".concat(projectName).concat(".po;");
	// java bean file path
	private static final String beanPath = "/src/main/java/com/emcom/".concat(projectName).concat("/po/");
	// command line help information.<br/>
	private static final String USAGE = "BeanUtils.class [-n <name>][-d <database>]";
	private static final String HEADER = "BATCH - EMCOM Advanced batch app program, Copyright 2008 batch term.";
	private static final String FOOTER = "For more information, see our website at: http://www.adv.emcom.jp";
	// colume desc.<br/>
	private static final int TABLE_CAT = 1;
	private static final int TABLE_SCHEM = 2;
	private static final int TABLE_NAME = 3;
	private static final int COLUMN_NAME = 4;
	private static final int DATA_TYPE = 5;
	private static final int TYPE_NAME = 6;
	private static final int COLUMN_SIZE = 7;
	private static final int DECIMAL_DIGITS = 9;
	// java and mysql data type mapping
	private static Map<String, String> columnTypeMap;
	// mysql , java init.<br/>
	static {
		columnTypeMap = new HashMap<String, String>();
		columnTypeMap.put("VARCHAR", "String");
		columnTypeMap.put("CHAR", "String");
		columnTypeMap.put("DECIMAL", "BigDecimal");
		columnTypeMap.put("FLOAT", "BigDecimal");
		columnTypeMap.put("TIMESTAMP", "Timestamp");
		columnTypeMap.put("DATETIME", "Timestamp");
		columnTypeMap.put("TEXT", "String");
		columnTypeMap.put("BLOB", "byte[]");
		columnTypeMap.put("INT UNSIGNED", "int");
		columnTypeMap.put("INT", "int");
		columnTypeMap.put("BIGINT UNSIGNED", "int");
		columnTypeMap.put("BIGINT", "long");
		columnTypeMap.put("ENUM", "String");
	}

	public static void main(String[] args) {
		// parse commandline for BeanUtils.<br/>
		Validate.notEmpty(args);
		String[] tableNames = null;
		// database schema.<br/>
		DB database = DB.GOTOUR;
		// persistence.<br/>
		boolean isPersistence = true;
		try {
			Options options = getDefaultOptions();
			CommandLineParser parser = new BasicParser();
			CommandLine commandLine = parser.parse(options, args);
			if(commandLine.hasOption("n")) {
				tableNames = commandLine.getOptionValues("n");
			}
			if(commandLine.hasOption("d")) {
				String db = commandLine.getOptionValue("d");
				database = DB.valueOf(db);
			}
			if(commandLine.hasOption("p")) {
				isPersistence = Boolean.valueOf(commandLine.getOptionValue("p"));
			}
		} catch (ParseException e) {
			printTaskRunnerUsage();
			System.exit(0);
		}
		Map<String, List<ColumeProperty>> tableMap = createTableMap(tableNames, database);
		Map<String, String> classMap = createClassContext(tableNames, tableMap);
		if(isPersistence) {
			createBeanFile(tableNames, classMap);
		}
		createIbatisHelpContext(tableNames, tableMap);
	}

	/**
	 * create table information map.<br/>
	 * @param tableNames
	 * @return
	 */
	private static Map<String, List<ColumeProperty>> createTableMap(String[] tableNames, DB database) {
		System.out.println("create table map started ... ");
		Validate.notNull(tableNames);
		// create table map.<br/>
		Map<String, List<ColumeProperty>> tableMap = new HashMap<String, List<ColumeProperty>>();
		for(String tableName : tableNames) {
			try {
				DatabaseMetaData databaseMetaData = (DatabaseMetaData)getDataSource(database).getConnection().getMetaData();
				ResultSet resultSet = databaseMetaData.getColumns(null, null, tableName, null);
				// create colume and property list.<br/>
				List<ColumeProperty> columePropertyList = new ArrayList<ColumeProperty>();
				while(resultSet.next()) {
					String columeName = resultSet.getString(COLUMN_NAME);
					String columeType = MapUtils.getString(columnTypeMap, resultSet.getString(TYPE_NAME));
					String propertyName = parseColumeName(columeName);
					ColumeProperty columeProperty = new ColumeProperty();
					columeProperty.setColumeName(columeName);
					columeProperty.setColumeType(columeType);
					columeProperty.setPropertyName(propertyName);
					columePropertyList.add(columeProperty);
				}
				tableMap.put(tableName, columePropertyList);
			} catch (SQLException e) {
				ExceptionUtils.getFullStackTrace(e);
			}
		}
		System.out.println("create table map completed. ");
		return tableMap;
	}

	private static Map<String, String> createClassContext(String[] tableNames, Map<String, List<ColumeProperty>> tableMap) {
		System.out.println("create class map started ... ");
		Map<String, String> classMap = new HashMap<String, String>();
		for(String tableName : tableNames) {
			if(tableMap.containsKey(tableName)) {
				List<ColumeProperty> columePropertyList = tableMap.get(tableName);
				StringBuffer beanContext = new StringBuffer();
				beanContext.append(packagePath + "\n");
				beanContext.append("/**\n" +
						" * database table " + tableName + " bean.<br/>\n" +
						" * @author " + author + "\n" +
						" * @version 1.0\n" +
						" */\n");
				// create colume annotation.<br/>
//				beanContext.append("@Colume(name={");
//				for(int i = 0; i < columePropertyList.size(); i++) {
//					ColumeProperty columeProperty = columePropertyList.get(i);
//					beanContext.append("\"");
//					beanContext.append(columeProperty.getColumeName());
//					beanContext.append("\"");
//					if(i != columePropertyList.size() - 1) {
//						beanContext.append(",");
//					}
//				}
//				beanContext.append("})\n");
				// create class context.<br/>
				String beanName = parseTableName(tableName);
				beanContext.append("public class " + beanName + " implements Comparable<Object>, Serializable {\n");
				for(int i = 0; i < columePropertyList.size(); i++) {
					ColumeProperty columeProperty = columePropertyList.get(i);
					beanContext.append("	private " + columeProperty.getColumeType() + " " + columeProperty.getPropertyName() + ";\n");
				}
				beanContext.append("}");
				classMap.put(tableName, beanContext.toString());
			}
		}
		System.out.println("create class map completed. ");
		return classMap;
	}

	/**
	 * create bean file.<br/>
	 * @param tableNames
	 * @param classMap
	 */
	private static void createBeanFile(String[] tableNames, Map<String, String> classMap) {
		System.out.println("create bean file started ... ");
		for(String tableName : tableNames) {
			if(classMap.containsKey(tableName)) {
				FileOutputStream fileout = null;
				String classContext = classMap.get(tableName);
				String beanName = parseTableName(tableName);
				String userDir = System.getProperties().getProperty("user.dir");
				File folder = new File(userDir.concat(beanPath));
				if (folder.exists()) {
					File file = new File(userDir.concat(beanPath).concat(beanName).concat(".java"));
					try {
						file.createNewFile();
						fileout = new FileOutputStream(file);
						BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileout);
						bufferedOutputStream.write(classContext.getBytes());
						bufferedOutputStream.close();
					} catch (IOException e) {
						ExceptionUtils.getFullStackTrace(e);
					}
				} else {
					System.out.println("Output dir not exist, Don't create class file.");
				}
			}
		}
		System.out.println("create bean file completed. ");
	}

	/**
	 * create ibatis help context.<br/>
	 * @param tableNames
	 * @param classMap
	 */
	private static void createIbatisHelpContext(String[] tableNames, Map<String, List<ColumeProperty>> tableMap) {
		System.out.println("create ibatis help context started ... ");
		for(String tableName : tableNames) {
			if(tableMap.containsKey(tableName)) {
				List<ColumeProperty> columePropertyList = tableMap.get(tableName);
				createIbatisTypeSql(tableName);
				createResultMap(tableName, columePropertyList);
				createIbatisSelectSql(tableName, columePropertyList);
				createIbatisUpdateSql(tableName, columePropertyList);
				createIbatisInsertSql(tableName, columePropertyList);
			}
		}
		System.out.println("create ibatis help context completed. ");
	}

	private static void createIbatisTypeSql(String tableName) {
		System.out.println("************************* TABLE: [".concat(tableName).concat("]'s TYPE SQL *************************"));
		String beanName = parseTableName(tableName);
		System.out.println("	<typeAlias alias=\"" + beanName.substring(0,1).toLowerCase().concat(beanName.substring(1)) + "\" type=\"" + packagePath.substring(8, packagePath.length() - 1).concat(".").concat(beanName) + "\"/>");
	}

	/**
	 * create ibatis select sql.<br/>
	 * @param tableName
	 * @param columePropertyList
	 */
	private static void createIbatisSelectSql(String tableName, List<ColumeProperty> columePropertyList) {
		System.out.println("************************* TABLE: [".concat(tableName).concat("]'s SELETE SQL *************************"));
		String beanName = parseTableName(tableName);
		System.out.println("	<select id=\"query" + beanName + "\" resultClass=\"" + beanName.substring(0,1).toLowerCase().concat(beanName.substring(1)) + "\">");
		System.out.println("		<![CDATA[");
		System.out.println("		SELECT");
		for(int i = 0; i < columePropertyList.size(); i++) {
			ColumeProperty columeProperty = columePropertyList.get(i);
			if(i == columePropertyList.size() - 1) {
				System.out.println("			".concat(columeProperty.getColumeName()).concat(" AS ").concat(columeProperty.getPropertyName()));
			} else {
				System.out.println("			".concat(columeProperty.getColumeName()).concat(" AS ").concat(columeProperty.getPropertyName()).concat(","));
			}
		}
		System.out.println("		FROM ".concat(tableName));
		System.out.println("		]]>");
		System.out.println("	</select>");
	}

	private static void createResultMap(String tableName, List<ColumeProperty> columePropertyList) {
		System.out.println("************************* TABLE: [".concat(tableName).concat("]'s RESULT MAP *************************"));
		String beanName = parseTableName(tableName);
		System.out.println("	<resultMap id=\"" + beanName + "\" class=\"" + packagePath.substring(8, packagePath.length() - 1).concat(".").concat(beanName) + "\">");
		for(int i = 0; i < columePropertyList.size(); i++) {
			ColumeProperty columeProperty = columePropertyList.get(i);
			System.out.println("		<result property=\"" + columeProperty.getPropertyName() + "\" column=\"" + columeProperty.getColumeName() + "\"/>");
		}
		System.out.println("	</resultMap>");
	}

	/**
	 * create ibatis update sql.<br/>
	 * @param tableName
	 * @param columePropertyList
	 */
	private static void createIbatisUpdateSql(String tableName, List<ColumeProperty> columePropertyList) {
		System.out.println("************************* TABLE: [".concat(tableName).concat("]'s UPDATE SQL *************************"));
		String beanName = parseTableName(tableName);
		System.out.println("	<update id=\"update" + beanName + "\">");
		System.out.println("		<![CDATA[");
		System.out.println("		UPDATE ".concat(tableName));
		System.out.println("		SET");
		for(int i = 0; i < columePropertyList.size(); i++) {
			ColumeProperty columeProperty = columePropertyList.get(i);
			if(i == columePropertyList.size() - 1) {
				System.out.println("			".concat(columeProperty.getColumeName()).concat(" = #").concat(columeProperty.getPropertyName()).concat("#"));
			} else {
				System.out.println("			".concat(columeProperty.getColumeName()).concat(" = #").concat(columeProperty.getPropertyName()).concat("#,"));
			}
		}
		System.out.println("		]]>");
		System.out.println("	</update>");
	}

	private static void createIbatisInsertSql(String tableName, List<ColumeProperty> columePropertyList) {
		System.out.println("************************* TABLE: [".concat(tableName).concat("]'s INSERT SQL *************************"));
		String beanName = parseTableName(tableName);
		System.out.println("	<insert id=\"insert" + beanName + "\">");
		System.out.println("		<![CDATA[");
		System.out.println("		INSERT INTO ".concat(tableName).concat("("));
		for(int i = 0; i < columePropertyList.size(); i++) {
			ColumeProperty columeProperty = columePropertyList.get(i);
			if(i == columePropertyList.size() - 1) {
				System.out.println("			".concat(columeProperty.getColumeName()));
			} else {
				System.out.println("			".concat(columeProperty.getColumeName()).concat(","));
			}
		}
		System.out.println("		) VALUES(");
		for(int i = 0; i < columePropertyList.size(); i++) {
			ColumeProperty columeProperty = columePropertyList.get(i);
			if(i == columePropertyList.size() - 1) {
				System.out.println("			#".concat(columeProperty.getPropertyName()).concat("#"));
			} else {
				System.out.println("			#".concat(columeProperty.getPropertyName()).concat("#").concat(","));
			}
		}
		System.out.println("		)");
		System.out.println("		]]>");
		System.out.println("	</insert>");
		
	}

	/**
	 * JdbcTemplate Support For Spring.<br/>
	 * @param database
	 * @return
	 */
	private static DataSource getDataSource(DB database) {
		// init spring application context
		String[] classPathXmlConfig = new String[] { "file:src/main/webapp/WEB-INF/config/core-properties.xml", "file:src/main/webapp/WEB-INF/config/core-datasource.xml" };
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext(classPathXmlConfig);
		// fetch database connection
		return (DataSource)applicationContext.getBean(database.value());
	}

	/**
	 * print command line help.<br/>
	 */
	private static void printTaskRunnerUsage() {
		HelpFormatter helpFormatter = new HelpFormatter();
		helpFormatter.setWidth(80);
		helpFormatter.printHelp(USAGE, HEADER, getDefaultOptions(), FOOTER);
	}

	/**
	 * command line default options.<br/>
	 * @return
	 */
	private static Options getDefaultOptions() {
		Options options = new Options();
		options.addOption(OptionBuilder.isRequired(true).hasArgs().withArgName("name").withDescription("table name").withLongOpt("name").create("n"));
		options.addOption(OptionBuilder.isRequired(false).hasArg(true).withArgName("database").withDescription("database").withLongOpt("db").create("d"));
		options.addOption(OptionBuilder.isRequired(false).hasArg(true).withArgName("persistence").withDescription("persistence").withLongOpt("p").create("p"));
		return options;
	}

	/**
	 * parse table name.<br/>
	 * @param tableName
	 * @return
	 */
	private static String parseTableName(String tableName) {
		String newTableName = "";
		String tableNameArray[] = tableName.split("_");
		for (String tableNameStr : tableNameArray) {
			String formatedStr = tableNameStr.substring(0, 1) + tableNameStr.substring(1).toLowerCase();
			newTableName = newTableName.concat(formatedStr);
		}
		return newTableName;
	}

	/**
	 * parse colume name.<br/>
	 * @param columnName
	 * @return
	 */
	private static String parseColumeName(String columnName) {
		String newColumnName = "";
		String[] columnNameArray = StringUtils.split(columnName, "_");
		boolean isFirst = true;
		for (String columnNameStr : columnNameArray) {
			if (isFirst) {
				String formatedStr = columnNameStr.toLowerCase();
				newColumnName = newColumnName.concat(formatedStr);
				isFirst = false;
			} else {
				String formatedStr = columnNameStr.substring(0, 1).toUpperCase().concat(columnNameStr.substring(1).toLowerCase());
				newColumnName = newColumnName.concat(formatedStr);
			}
		}
		return newColumnName;
	}

	enum DB {
		GOTOUR("gotourDataSource"),OTHERS("dataSource");
		private String db;
		private DB(String db) {
			this.db = db;
		}
		public String value() {
			return this.db;
		}
	}

}
